跳到主要内容

Go 单元测试

Unit Test Benchmark Test API Test

单元测试常用参数

这里介绍几个常用的参数: -v 详细输出,运行期间所有测试的日志。 -bench regexp 执行相应的 benchmarks,例如 -bench=.; -cover 开启测试覆盖率; -run regexp 只运行 regexp 匹配的函数,例如 -run=Array 那么就执行包含有 Array 开头的函数; -coverpkg pkg1,pkg2,pkg3 指定分析哪个包,默认值只分析被测试的包,包为导入的路径。

-c 编译测试二进制文件为 [pkg].test,不运行测试。

# 生成 sum 的二进制测试文件,并执行 
go test -c && ./sum.test

-o file 编译测试二进制文件并指定文件,同时运行测试。

go test -o filename

testing T 结构体

Golang 为这个 test 包提供了一个 testing.T 对象

package main

import "testing"

func TestAdd(t *testing.T) {
if ans := Add(1, 2); ans != 3 {
t.Errorf("1 + 2 expected be 3, but %d got", ans)
}

if ans := Add(-10, -20); ans != -30 {
t.Errorf("-10 + -20 expected be -30, but %d got", ans)
}
}

常用方法

当测试函数返回时,或者当测试函数调用 FailNow、 Fatal、Fatalf、SkipNow、Skip、Skipf 中的任意一个时,则宣告该测试函数结束。

t.Skip()

当我们遇到一个断言错误的时候,标识这个测试失败,会使用到:

Fail : 测试失败,测试继续,也就是之后的代码依然会执行
FailNow : 测试失败,测试中断

在 FailNow 方法实现的内部,是通过调用 runtime.Goexit() 来中断测试的。

当我们遇到一个断言错误,只希望跳过这个错误,但是不希望标识测试失败,会使用到:

SkipNow : 跳过测试,测试中断

在 SkipNow 方法实现的内部,是通过调用 runtime.Goexit() 来中断测试的。

打印日志

当我们只希望打印信息,会用到:

Log : 输出信息
Logf : 输出格式化的信息

注意:默认情况下,单元测试成功时,它们打印的信息不会输出,可以通过加上 -v 选项,输出这些信息。但对于基准测试,它们总是会被输出。

当我们希望跳过这个测试,并且打印出信息,会用到:

Skip : 相当于 Log + SkipNow
Skipf : 相当于 Logf + SkipNow

当我们希望断言失败的时候,标识测试失败,并打印出必要的信息,但是测试继续,会用到:

Error : 相当于 Log + Fail
Errorf : 相当于 Logf + Fail

当我们希望断言失败的时候,标识测试失败,打印出必要的信息,但中断测试,会用到:

Fatal : 相当于 Log + FailNow
Fatalf : 相当于 Logf + FailNow

测试代码覆盖率方法

首先代码覆盖率是什么?

语句的覆盖率是指在测试中至少被运行一次的代码占总代码数的比例。

编写一个单元测试

func Test_StartMain(t *testing.T) {
main()
}

运行命令

go test -coverpkg="./..." -c -o cover.test

# 这个 -test.coverprofile 用于
./cover.test -test.run "Test_StartMain" -test.coverprofile=cover.out

go tool cover -html cover.out -o cover.html

这里的 -c 参数可以编译生成测试二进制文件,但不运行,这个编译后的测试文件可以单独执行 这里的 -o 参数是用于搭配上面的 -c 使用,用于指定这个测试文件的名称

第一行的 go test 生成一个 coverage profile,这玩意也是一个完整的程序(类似于 build),只是执行是附加了一些用来统计覆盖率之类操作

第二行执行后(和普通 build 出来的程序是一样是可以正常执行的),通过使用 -coverprofile 用来指定覆盖率信息写入到哪个文件

这个 go tool cover 则是用来分析上面执行完后输出的覆盖率结果

生成如下内容

image.png

这时可以点开这个 html(绿色是指单元测试覆盖到这行代码了)

imageb3c79f828afea048.png

如果想以命令行的方式打印这个测试覆盖率,可以使用 -func

# 可以打印各个用到的方法的覆盖率
go tool cover -func cover.out

# 可以只显示总覆盖率
go tool cover -func cover.out | grep total

子测试 Run

子测试在 testing 包中由 Run 方法 提供,它有俩个参数:子测试的名字和子测试函数,其中名字是子测试的标识符。

子测试和其他普通的测试函数一样,是在独立的 goroutine 中运行,测试结果也会计入测试报告,所有子测试运行完毕后,父测试函数才会结束。

// calc_test.go

func TestMul(t *testing.T) {
t.Run("pos", func(t *testing.T) {
if Mul(2, 3) != 6 {
t.Fatal("fail")
}

})
t.Run("neg", func(t *testing.T) {
if Mul(2, -3) != -6 {
t.Fatal("fail")
}
})
}

之前的例子测试失败时使用 t.Error/t.Errorf,这个例子中使用 t.Fatal/t.Fatalf,区别在于前者遇错不停,还会继续执行其他的测试用例,后者遇错即停。

运行某个测试用例的子测试:

$ go test -run TestMul/pos -v
=== RUN TestMul
=== RUN TestMul/pos
--- PASS: TestMul (0.00s)
--- PASS: TestMul/pos (0.00s)
PASS
ok example 0.008s

Reference

Golang 与子测试 Go Test 单元测试简明教程